﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;

namespace CoreLibrary.Collection {

    /// <summary>
    /// Thread safe dictionary with richer functionalities.
    /// </summary>
    /// <typeparam name="TKey"></typeparam>
    /// <typeparam name="TValue"></typeparam>
    public class ReadWriteDictionary<TKey, TValue> : IDictionary<TKey, TValue> {
        
        private readonly Dictionary<TKey, TValue> _dict;
        public  readonly ReaderWriterLockSlim Lock = new ReaderWriterLockSlim();

        /// <summary>
        /// Initializes the dictionary object
        /// </summary>
        public ReadWriteDictionary() {
            _dict = new Dictionary<TKey, TValue>();
        }

        /// <summary>
        /// Initializes the dictionary object
        /// </summary>
        /// <param name="capacity">initial capacity of the dictionary</param>
        public ReadWriteDictionary(int capacity) {
            _dict = new Dictionary<TKey, TValue>(capacity);
        }

        /// <summary>
        /// Initializes the dictionary object
        /// </summary>
        /// <param name="comparer">the comparer to use when comparing keys</param>
        public ReadWriteDictionary(IEqualityComparer<TKey> comparer) {
            _dict = new Dictionary<TKey, TValue>(comparer);
        }

        /// <summary>
        /// Initializes the dictionary object
        /// </summary>
        /// <param name="dictionary">the dictionary whose keys and values are copied to this object</param>
        public ReadWriteDictionary(IDictionary<TKey, TValue> dictionary) {
            _dict = new Dictionary<TKey, TValue>(dictionary);
        }

        /// <summary>
        /// Initializes the dictionary object
        /// </summary>
        /// <param name="capacity">initial capacity of the dictionary</param>
        /// <param name="comparer">the comparer to use when comparing keys</param>
        public ReadWriteDictionary(int capacity, IEqualityComparer<TKey> comparer) {
            _dict = new Dictionary<TKey, TValue>(capacity, comparer);
        }

        /// <summary>
        /// Initializes the dictionary object
        /// </summary>
        /// <param name="dictionary">the dictionary whose keys and values are copied to this object</param>
        /// <param name="comparer">the comparer to use when comparing keys</param>
        public ReadWriteDictionary(IDictionary<TKey, TValue> dictionary, IEqualityComparer<TKey> comparer) {
            _dict = new Dictionary<TKey, TValue>(dictionary, comparer);
        }

        /// <summary>
        /// Adds an item to the dictionary
        /// </summary>
        /// <param name="key">the key to add</param>
        /// <param name="value">the value to add</param>
        public void Add(TKey key, TValue value) {
            Lock.EnterWriteLock();
            try {
                _dict.Add(key, value);
            }
            finally {
                Lock.ExitWriteLock();
            }
        }

        public void Add(KeyValuePair<TKey, TValue> item) {
            Lock.EnterWriteLock();
            try {
                _dict.Add(item.Key, item.Value);
            }
            finally {
                Lock.ExitWriteLock();
            }
        }

        /// <summary>
        /// Returns true if the key value pair exists in the dictionary
        /// </summary>
        /// <param name="item">key value pair to find</param>
        public bool Contains(KeyValuePair<TKey, TValue> item) {
            Lock.EnterReadLock();
            try {
                return ((_dict.ContainsKey(item.Key)) &&
                        (_dict.ContainsValue(item.Value)));
            }
            finally {
                Lock.ExitReadLock();
            }
        }

        /// <summary>
        /// Returns true if the key exists in the dictionary
        /// </summary>
        /// <param name="key">the key to find in the dictionary</param>
        public bool ContainsKey(TKey key) {
            Lock.EnterReadLock();
            try {
                return _dict.ContainsKey(key);
            }
            finally {
                Lock.ExitReadLock();
            }
        }

        public TValue Get(TKey key) {
            Lock.EnterReadLock();
            try {
                TValue value;
                if (_dict.TryGetValue(key, out value)) {
                    return value;
                }
                return default(TValue);
            }
            finally {
                Lock.ExitReadLock();
            }
        }

        /// <summary>
        /// Returns the keys as a collection
        /// </summary>
        public ICollection<TKey> Keys {
            get {
                Lock.EnterReadLock();
                try {
                    return _dict.Keys;
                }
                finally {
                    Lock.ExitReadLock();
                }
            }
        }

        public void Put(TKey key, TValue value) {
			Lock.EnterWriteLock();
			try {
				_dict[key] = value;
			}
			finally {
				Lock.ExitWriteLock();
			}
        }

        public void PutAll(IDictionary<TKey, TValue> parameters) {
            Lock.EnterWriteLock();
            try {
                foreach (var parameter in parameters) {
                    _dict[parameter.Key] = parameter.Value;
                }
            }
            finally {
                Lock.ExitWriteLock();
            }
        }

        /// <summary>
        /// Removes the element with this key name
        /// </summary>
        /// <param name="key">the key to remove</param>
        public bool Remove(TKey key) {
            Lock.EnterWriteLock();
            try {
                return _dict.Remove(key);
            }
            finally {
                Lock.ExitWriteLock();
            }
        }

        /// <summary>
        /// Removes the element with this key name and value. Returns
        /// true if the item was removed.
        /// </summary>
        /// <param name="item">the key to remove</param>
        public bool Remove(KeyValuePair<TKey, TValue> item) {
            Lock.EnterWriteLock();
            try {
                // skip if the key doesn't exist
                TValue tempVal;
                if (!_dict.TryGetValue(item.Key, out tempVal))
                    return false;

                // skip if the value's don't match
                if (!tempVal.Equals(item.Value))
                    return false;

                return _dict.Remove(item.Key);
            }
            finally {
                Lock.ExitWriteLock();
            }
        }

        public void ReplaceAll(IDictionary<TKey, TValue> parameters) {
            Lock.EnterWriteLock();
            try {
                _dict.Clear();
                foreach (var parameter in parameters) {
                    _dict[parameter.Key] = parameter.Value;
                }
            }
            finally {
                Lock.ExitWriteLock();
            }
        }

        /// <summary>
        /// Attemtps to return the value found at element <paramref name="key"/>
        /// If no value is found, returns false
        /// </summary>
        /// <param name="key">the key to find</param>
        /// <param name="value">the value returned if the key is found</param>
        public bool TryGetValue(TKey key, out TValue value) {
            Lock.EnterReadLock();
            try {
                return _dict.TryGetValue(key, out value);
            }
            finally {
                Lock.ExitReadLock();
            }
        }

        /// <summary>
        /// Returns a collection of the values in the dictionary
        /// </summary>
        public ICollection<TValue> Values {
            get {
                Lock.EnterReadLock();
                try {
                    return _dict.Values;
                }
                finally {
                    Lock.ExitReadLock();
                }
            }
        }

        public TValue this[TKey key] {
            get {
                Lock.EnterReadLock();
                try {
                    return _dict[key];
                }
                finally {
                    Lock.ExitReadLock();
                }
            }
            set {
                Lock.EnterWriteLock();
                try {
                    _dict[key] = value;
                }
                finally {
                    Lock.ExitWriteLock();
                }
            }
        }

        /// <summary>
        /// Clears the dictionary
        /// </summary>
        public void Clear() {
            Lock.EnterWriteLock();
            try {
                _dict.Clear();
            }
            finally {
                Lock.ExitWriteLock();
            }
        }

        /// <summary>
        /// Copies the items of the dictionary to a key value pair array
        /// </summary>
        /// <param name="array">the key value pair collection to copy into</param>
        /// <param name="arrayIndex">the index to begin copying to</param>
        public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex) {
            Lock.EnterReadLock();
            try {
                _dict.ToArray().CopyTo(array, arrayIndex);
            }
            finally {
                Lock.ExitReadLock();
            }
        }

        /// <summary>
        /// Returns the number of items in the dictionary
        /// </summary>
        public int Count {
            get {
                Lock.EnterReadLock();
                try {
                    return _dict.Count;
                }
                finally {
                    Lock.ExitReadLock();
                }
            }
        }

        public bool IsReadOnly {
            get { return false; }
        }

        public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() {
            Lock.EnterReadLock();
            try {
                return ((IEnumerable<KeyValuePair<TKey, TValue>>)new Dictionary<TKey, TValue>(_dict)).GetEnumerator();
            }
            finally {
                Lock.ExitReadLock();
            }
        }

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() {
            Lock.EnterReadLock();
            try {
                return new Dictionary<TKey, TValue>(_dict).GetEnumerator();
            }
            finally {
                Lock.ExitReadLock();
            }
        }

        /// <summary>
        /// Returns the value of <paramref name="key"/>. If <paramref name="key"/>
        /// does not exist, <paramref name="func"/> is performed and added to the 
        /// dictionary
        /// </summary>
        /// <param name="key">the key to check</param>
        /// <param name="func">the delegate to call if key does not exist</param>
        public TValue GetValueAddIfNotExist(TKey key, Func<TValue> func) {

            Lock.EnterReadLock();
            try {
                TValue value;
                if (_dict.TryGetValue(key, out value)) {
                    return value;
                }
            }
            finally {
                Lock.ExitReadLock();
            }
            
            // enter a write lock, to make absolutely sure that the key
            // is added/deleted from the time we check if it exists
            // to the time we add it if it doesn't exist
            Lock.EnterWriteLock();
            try {
                TValue value;

                // if we have the value, get it and exit
                if (_dict.TryGetValue(key, out value)) {
                    return value;
                }

                value = func.Invoke();
                _dict.Add(key, value);
                return value;
            }
            finally {
                Lock.ExitWriteLock();
            }
        }

        /// <summary>
        /// Adds the value if it's key does not already exist. Returns
        /// true if the value was added
        /// </summary>
        /// <param name="key">the key to check, add</param>
        /// <param name="value">the value to add if the key does not already exist</param>
        public bool AddIfNotExists(TKey key, TValue value) {
            bool added = false;

            Lock.EnterWriteLock();
            try {
                // if not exist, then add it
                if (!_dict.ContainsKey(key)) {
                    // add the value and set the flag
                    _dict.Add(key, value);
                    added = true;
                }
            }
            finally {
                Lock.ExitWriteLock();
            }

            return added;
        }

        /// <summary>
        /// Adds the list of value if the keys do not already exist.
        /// </summary>
        /// <param name="keys">the keys to check, add</param>
        /// <param name="defaultValue">the value to add if the key does not already exist</param>
        public void AddIfNotExists(IEnumerable<TKey> keys, TValue defaultValue) {
            Lock.EnterWriteLock();
            try {
                foreach (TKey key in keys) {
                    // if not exist, then add it
                    if (!_dict.ContainsKey(key))
                        _dict.Add(key, defaultValue);
                }
            }
            finally {
                Lock.ExitWriteLock();
            }
        }

        /// <summary>
        /// Updates the value of the key if the key exists. Returns true if updated
        /// </summary>
        /// <param name="key"></param>
        /// <param name="newValue"></param>
        public bool UpdateValueIfKeyExists(TKey key, TValue newValue) {
            bool rVal = false;

            Lock.EnterWriteLock();
            try {
                // if we have the key, then update it
                if (_dict.ContainsKey(key)) {
                    _dict[key] = newValue;
                    rVal = true;
                }
            }

            finally {
                Lock.ExitWriteLock();
            }

            return rVal;
        }

        /// <summary>
        /// Returns true if the dictionary contains this value
        /// </summary>
        /// <param name="value">the value to find</param>
        public bool ContainsValue(TValue value) {
            Lock.EnterReadLock();
            try {
                return _dict.ContainsValue(value);
            }
            finally {
                Lock.ExitReadLock();
            }
        }

        /// <summary>
        /// Removes items from the dictionary that match a pattern. Returns true
        /// on success
        /// </summary>
        /// <param name="predKey">Optional expression based on the keys</param>
        /// <param name="predValue">Option expression based on the values</param>
        public bool Remove(Predicate<TKey> predKey, Predicate<TValue> predValue) {
            bool removed;

            Lock.EnterWriteLock();
            try {
                // exit if no keys
                if (_dict.Keys.Count == 0)
                    return true;

                // holds the list of items to be deleted
                var deleteList = new List<TKey>();

                // process keys
                foreach (TKey key in _dict.Keys) {
                    bool isMatch = false;

                    // add the item to the list if it matches the predicate
                    if (predKey != null)
                        isMatch = (predKey(key));

                    // if this item's value matches, add it
                    if ((!isMatch) && (predValue != null) && (predValue(_dict[key])))
                        isMatch = true;

                    // add to the list if we have a match
                    if (isMatch)
                        deleteList.Add(key);
                }

                // delete all the items from the list
                foreach (TKey item in deleteList)
                    _dict.Remove(item);

                removed = true;
            }

            finally {
                Lock.ExitWriteLock();
            }

            return removed;
        }

        public int Size() {
            return Count;
        }

        public override int GetHashCode() {
            return _dict.Count;
        }

        public bool Equals(ReadWriteDictionary<TKey, TValue> d) {
            if (d == null || d.Count != Count) {
                return false;
            }
            return d.All(entry => Get(entry.Key).Equals(entry.Value));
        }

        public override bool Equals(object obj) {
            if (ReferenceEquals(null, obj)) return false;
            if (ReferenceEquals(this, obj)) return true;
            var d = obj as ReadWriteDictionary<TKey, TValue>;
            if (d == null) return false;
            return Equals(d);
        }

    }
}